package com.googlecode.imanager.client.util;

import java.io.Serializable;
import java.util.AbstractList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

/**
 * @author wellsantos@wellsantos.com
 * @since 0.01
 */
public class SharedList<Child> extends AbstractList<Child> implements Serializable {

    private static final long serialVersionUID = 1L;
    
    protected List<Child> childList;
    
    
    public SharedList() {
        this(10);
    }
    
    public SharedList(int capacity) {
        this.childList = createList(capacity);
    }
    
    public SharedList(Collection<? extends Child> list) {
        if (list != null) {
            this.childList = createList(list.size());
            addAll(list);
        } else {
            this.childList = createList(10);
        }
    }

    
    protected List<Child> createList(int capacity) {
        return new ArrayList<Child>(capacity);
    }

    protected void internalAdd(int index, Child item) {
        childList.add(index, item);
    }
    
    protected Child internalSet(int index, Child item) {
        return childList.set(index, item);
    }
    
    protected void internalRemove(int index, Child item) {
        childList.remove(index);
    }
    
    @Override
    public int indexOf(Object o) {
        return childList.indexOf(o);
    }

    @Override
    public Child get(int index) {
        return childList.get(index);
    }
    
    @Override
    public int size() {
        return childList.size();
    }

    
    protected boolean isDuplicated(Child item){
        return false;
    }
    
    protected boolean isValidItem(Child item){
        return true;
    }
    
    protected void validateItem(Child item){
        if (!isValidItem(item)) {
            throw new RuntimeException("can't add a NULL object");
        }
    }
    
    protected void checkForDuplication(Child item){
        if (isDuplicated(item)) {
            throw new RuntimeException("can't add the same object twice");
        }
    }
    
    
    @Override
    public boolean contains(Object item) {
        return indexOf(item) >= 0;
    }
    
    public SharedList<Child> append(Child item) {
        add(item);
        return this;
    }

    public void tryAdd(Child item) throws RuntimeException {
        validateItem(item);
        checkForDuplication(item);

        int index = size();
        onBeforeAdd(index, item);
        internalAdd(index, item);
        onAfterAdd(index, item);
    }
    
    @Override
    public boolean add(Child item) {
        if (isValidItem(item) && !isDuplicated(item)) {
            int index = size();
            //try {
                onBeforeAdd(index, item);
                internalAdd(index, item);
                onAfterAdd(index, item);
            //} catch (RuntimeException e) {
                //return false;
            //}
            return true;
        }
        return false;
    }

    @Override
    public void add(int index, Child item) {
        validateItem(item);
        if (!isDuplicated(item)){
            onBeforeAdd(index, item);
            internalAdd(index, item);
            onAfterAdd(index, item);
        }
    }

    @Override
    public Child set(int index, Child item) {
        validateItem(item);
        Child oldItem = get(index);
        if (Utils.isEquals(oldItem, item)) {
            checkForDuplication(oldItem);
            try {
                oldItem = internalSet(index, null);
                onBeforeReplace(index, oldItem, item);
                internalSet(index, item);
            } catch (RuntimeException e){
                // we should restore the old item before throws the error
                internalSet(index, oldItem);
                throw e;
            }
            onAfterReplace(index, oldItem, item);
        } else {
            onModified(index, item);
            return oldItem;
        }
        return oldItem;
    }
    
    @Override
    public Child remove(int index) {
        Child item = get(index);
        onBeforeRemove(index, item);
        internalRemove(index, item);
        onAfterRemove(index, item);
        return item;
    }

    @Override
    @SuppressWarnings("unchecked")
    public boolean remove(Object item) {
        int index = indexOf(item);
        if (index >= 0) {
            Child child = (Child) item;
            onBeforeRemove(index, child);
            internalRemove(index, child);
            onAfterRemove(index, child);
            return true;
        }
        return false;
    }

    /**
     * Note: this event is fired before the item be added.
     * 
     * @param index
     * @param item
     */
    protected void onBeforeAdd(int index, Child item) {
    }

    /**
     * Note: this event is fired after the item be added.
     * 
     * @param index
     * @param item
     */
    protected void onAfterAdd(int index, Child item) {
    }

    /**
     * Note: this event is fired before the item be removed.
     * 
     * @param item
     */
    protected void onBeforeRemove(int index, Child item) {
    }

    /**
     * Note: this event is fired after the item be removed.
     * 
     * @param item
     */
    protected void onAfterRemove(int index, Child item) {
    }

    /**
     * Note: this event is thrown after the <code>oldItem</code> be removed and
     * before the <code>newItem</code> be added. </br> If an RuntimeException
     * occurs the <code>oldItem</code> will be internally restored.
     * 
     * @param index
     * @param oldItem
     * @param newItem
     */
    protected void onBeforeReplace(int index, Child oldItem, Child newItem) {
    }

    protected void onAfterReplace(int index, Child oldItem, Child newItem) {
    }

    protected void onModified(int index, Child item) {
    }

}
